using System;
using System.Diagnostics;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.core
{


	using TransactionManager = javax.transaction.TransactionManager;

	using Direction = org.neo4j.graphdb.Direction;
	using Node = org.neo4j.graphdb.Node;
	using NotFoundException = org.neo4j.graphdb.NotFoundException;
	using Relationship = org.neo4j.graphdb.Relationship;
	using RelationshipType = org.neo4j.graphdb.RelationshipType;
	using ReturnableEvaluator = org.neo4j.graphdb.ReturnableEvaluator;
	using StopEvaluator = org.neo4j.graphdb.StopEvaluator;
	using Traverser = org.neo4j.graphdb.Traverser;
	using Order = org.neo4j.graphdb.Traverser.Order;
	using AdaptiveCacheManager = org.neo4j.kernel.impl.cache.AdaptiveCacheManager;
	using Cache = org.neo4j.kernel.impl.cache.Cache;
	using LruCache = org.neo4j.kernel.impl.cache.LruCache;
	using SoftLruCache = org.neo4j.kernel.impl.cache.SoftLruCache;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using PropertyIndexData = org.neo4j.kernel.impl.nioneo.store.PropertyIndexData;
	using RelationshipChainPosition = org.neo4j.kernel.impl.nioneo.store.RelationshipChainPosition;
	using RelationshipData = org.neo4j.kernel.impl.nioneo.store.RelationshipData;
	using RelationshipTypeData = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData;
	using IdGenerator = org.neo4j.kernel.impl.persistence.IdGenerator;
	using PersistenceManager = org.neo4j.kernel.impl.persistence.PersistenceManager;
	using LockException = org.neo4j.kernel.impl.transaction.LockException;
	using LockManager = org.neo4j.kernel.impl.transaction.LockManager;
	using LockType = org.neo4j.kernel.impl.transaction.LockType;
	using InternalTraverserFactory = org.neo4j.kernel.impl.traversal.InternalTraverserFactory;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;
	using IntArray = org.neo4j.kernel.impl.util.IntArray;

	public class NodeManager
	{
		private static Logger log = Logger.getLogger(typeof(NodeManager).Name);

		private int referenceNodeId = 0;

		private readonly Cache<int, NodeImpl> nodeCache;
		private readonly Cache<int, RelationshipImpl> relCache;
		private readonly AdaptiveCacheManager cacheManager;
		private readonly LockManager lockManager;
		private readonly TransactionManager transactionManager;
		private readonly LockReleaser lockReleaser;
		private readonly PropertyIndexManager propertyIndexManager;
		private readonly InternalTraverserFactory traverserFactory;
		private readonly RelationshipTypeHolder relTypeHolder;
		private readonly PersistenceManager persistenceManager;
		private readonly IdGenerator idGenerator;

		private bool useAdaptiveCache = true;
		private float adaptiveCacheHeapRatio = 0.77f;
		private int minNodeCacheSize = 0;
		private int minRelCacheSize = 0;
		private int maxNodeCacheSize = 1500;
		private int maxRelCacheSize = 3500;

		private const int LOCK_STRIPE_COUNT = 5;
		private readonly ReentrantLock[] loadLocks = new ReentrantLock[LOCK_STRIPE_COUNT];

		internal NodeManager(AdaptiveCacheManager cacheManager, LockManager lockManager, LockReleaser lockReleaser, TransactionManager transactionManager, PersistenceManager persistenceManager, IdGenerator idGenerator, bool useNewCaches)
		{
			this.cacheManager = cacheManager;
			this.lockManager = lockManager;
			this.transactionManager = transactionManager;
			this.propertyIndexManager = new PropertyIndexManager(transactionManager, persistenceManager, idGenerator);
			this.lockReleaser = lockReleaser;
			lockReleaser.setNodeManager(this);
			lockReleaser.setPropertyIndexManager(propertyIndexManager);
			this.persistenceManager = persistenceManager;
			this.idGenerator = idGenerator;
			this.traverserFactory = new InternalTraverserFactory();
			this.relTypeHolder = new RelationshipTypeHolder(transactionManager, persistenceManager, idGenerator);
			if (useNewCaches)
			{
				nodeCache = new SoftLruCache<int, NodeImpl>("NodeCache");
				relCache = new SoftLruCache<int, RelationshipImpl>("RelationshipCache");
			}
			else
			{
				nodeCache = new LruCache<int, NodeImpl>("NodeCache", 1500, this.cacheManager);
				relCache = new LruCache<int, RelationshipImpl>("RelationshipCache", 3500, this.cacheManager);
			}
			for (int i = 0; i < loadLocks.Length; i++)
			{
				loadLocks[i] = new ReentrantLock();
			}
		}

		private void parseParams(Map<object, object> @params)
		{
			if (@params.containsKey("use_adaptive_cache"))
			{
				string @value = (string) @params.get("use_adaptive_cache");
				if (@value.ToLower().Equals("yes"))
				{
					useAdaptiveCache = true;
				}
				else if (@value.ToLower().Equals("no"))
				{
					useAdaptiveCache = false;
				}
				else
				{
					log.warning("Unable to parse use_adaptive_cache=" + @value);
				}
			}
			if (@params.containsKey("adaptive_cache_heap_ratio"))
			{
				object @value = @params.get("adaptive_cache_heap_ratio");
				try
				{
					adaptiveCacheHeapRatio = Convert.ToSingle((string) @value);
				}
				catch (NumberFormatException e)
				{
					log.warning("Unable to parse adaptive_cache_heap_ratio " + @value);
				}
				if (adaptiveCacheHeapRatio < 0.1f)
				{
					adaptiveCacheHeapRatio = 0.1f;
				}
				if (adaptiveCacheHeapRatio > 0.95f)
				{
					adaptiveCacheHeapRatio = 0.95f;
				}
			}
			if (@params.containsKey("min_node_cache_size"))
			{
				object @value = @params.get("min_node_cache_size");
				try
				{
					minNodeCacheSize = Convert.ToInt32((string) @value);
				}
				catch (NumberFormatException e)
				{
					log.warning("Unable to parse min_node_cache_size " + @value);
				}
			}
			if (@params.containsKey("min_relationship_cache_size"))
			{
				object @value = @params.get("min_relationship_cache_size");
				try
				{
					minRelCacheSize = Convert.ToInt32((string) @value);
				}
				catch (NumberFormatException e)
				{
					log.warning("Unable to parse min_relationship_cache_size " + @value);
				}
			}
			if (@params.containsKey("max_node_cache_size"))
			{
				object @value = @params.get("max_node_cache_size");
				try
				{
					maxNodeCacheSize = Convert.ToInt32((string) @value);
				}
				catch (NumberFormatException e)
				{
					log.warning("Unable to parse max_node_cache_size " + @value);
				}
			}
			if (@params.containsKey("max_relationship_cache_size"))
			{
				object @value = @params.get("max_relationship_cache_size");
				try
				{
					maxRelCacheSize = Convert.ToInt32((string) @value);
				}
				catch (NumberFormatException e)
				{
					log.warning("Unable to parse max_relationship_cache_size " + @value);
				}
			}
		}

		public virtual void start(Map<object, object> @params)
		{
			parseParams(@params);
			nodeCache.resize(maxNodeCacheSize);
			relCache.resize(maxRelCacheSize);
			if (useAdaptiveCache)
			{
				cacheManager.registerCache(nodeCache, adaptiveCacheHeapRatio, minNodeCacheSize);
				cacheManager.registerCache(relCache, adaptiveCacheHeapRatio, minRelCacheSize);
				cacheManager.Start(@params);
			}
		}

		public virtual void stop()
		{
			if (useAdaptiveCache)
			{
				cacheManager.Stop();
				cacheManager.unregisterCache(nodeCache);
				cacheManager.unregisterCache(relCache);
			}
			relTypeHolder.Clear();
		}

		public virtual Node createNode()
		{
			int id = idGenerator.nextId(typeof(Node));
			NodeImpl node = new NodeImpl(id, true, this);
			acquireLock(node, LockType.WRITE);
			bool success = false;
			try
			{
				persistenceManager.nodeCreate(id);
				nodeCache.put((int) node.getId(), node);
				success = true;
				return new NodeProxy(id, this);
			}
			finally
			{
				releaseLock(node, LockType.WRITE);
				if (!success)
				{
					setRollbackOnly();
				}
			}
		}

		public virtual Relationship createRelationship(Node startNode, Node endNode, RelationshipType type)
		{
			if (startNode == null || endNode == null || type == null)
			{
				throw new IllegalArgumentException("Null parameter, startNode=" + startNode + ", endNode=" + endNode + ", type=" + type);
			}

			if (!relTypeHolder.isValidRelationshipType(type))
			{
				relTypeHolder.addValidRelationshipType(type.name(), true);
			}
			int startNodeId = (int) startNode.getId();
			NodeImpl firstNode = getLightNode(startNodeId);
			if (firstNode == null)
			{
				setRollbackOnly();
				throw new NotFoundException("First node[" + startNode.getId() + "] deleted");
			}
			int endNodeId = (int) endNode.getId();
			NodeImpl secondNode = getLightNode(endNodeId);
			if (secondNode == null)
			{
				setRollbackOnly();
				throw new NotFoundException("Second node[" + endNode.getId() + "] deleted");
			}
			int id = idGenerator.nextId(typeof(Relationship));
			RelationshipImpl rel = new RelationshipImpl(id, startNodeId, endNodeId, type, true, this);
			bool firstNodeTaken = false;
			bool secondNodeTaken = false;
			acquireLock(rel, LockType.WRITE);
			bool success = false;
			try
			{
				acquireLock(firstNode, LockType.WRITE);
				firstNodeTaken = true;
				acquireLock(secondNode, LockType.WRITE);
				secondNodeTaken = true;
				int typeId = getRelationshipTypeIdFor(type);
				persistenceManager.relationshipCreate(id, typeId, startNodeId, endNodeId);
				firstNode.addRelationship(type, id);
				secondNode.addRelationship(type, id);
				relCache.put((int) rel.getId(), rel);
				success = true;
				return new RelationshipProxy(id, this);
			}
			finally
			{
				bool releaseFailed = false;
				if (firstNodeTaken)
				{
					try
					{
						releaseLock(firstNode, LockType.WRITE);
					}
					catch (Exception e)
					{
						releaseFailed = true;
						e.printStackTrace();
						log.severe("Failed to release lock");
					}
				}
				if (secondNodeTaken)
				{
					try
					{
						releaseLock(secondNode, LockType.WRITE);
					}
					catch (Exception e)
					{
						releaseFailed = true;
						e.printStackTrace();
						log.severe("Failed to release lock");
					}
				}
				releaseLock(rel, LockType.WRITE);
				if (!success)
				{
					setRollbackOnly();
				}
				if (releaseFailed)
				{
					throw new LockException("Unable to release locks [" + startNode + "," + endNode + "] in relationship create->" + rel);
				}
			}
		}

		private ReentrantLock lockId(int id)
		{
			int stripe = id % LOCK_STRIPE_COUNT;
			if (stripe < 0)
			{
				stripe *= -1;
			}
			ReentrantLock @lock = loadLocks[stripe];
			@lock.lock();
			return @lock;
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public Node getNodeById(int nodeId) throws NotFoundException
		public virtual Node getNodeById(int nodeId)
		{
			NodeImpl node = nodeCache.get(nodeId);
			if (node != null)
			{
				return new NodeProxy(nodeId, this);
			}
			node = new NodeImpl(nodeId, this);
			ReentrantLock loadLock = lockId(nodeId);
			try
			{
				if (nodeCache.get(nodeId) != null)
				{
					return new NodeProxy(nodeId, this);
				}
				if (!persistenceManager.loadLightNode(nodeId))
				{
					throw new NotFoundException("Node[" + nodeId + "]");
				}
				nodeCache.put(nodeId, node);
				return new NodeProxy(nodeId, this);
			}
			finally
			{
				loadLock.unlock();
			}
		}

		internal virtual NodeImpl getLightNode(int nodeId)
		{
			NodeImpl node = nodeCache.get(nodeId);
			if (node != null)
			{
				return node;
			}
			node = new NodeImpl(nodeId, this);
			ReentrantLock loadLock = lockId(nodeId);
			try
			{
				if (nodeCache.get(nodeId) != null)
				{
					node = nodeCache.get(nodeId);
					return node;
				}
				if (!persistenceManager.loadLightNode(nodeId))
				{
					return null;
				}
				nodeCache.put(nodeId, node);
				return node;
			}
			finally
			{
				loadLock.unlock();
			}
		}

		internal virtual NodeImpl getNodeForProxy(int nodeId)
		{
			NodeImpl node = nodeCache.get(nodeId);
			if (node != null)
			{
				return node;
			}
			node = new NodeImpl(nodeId, this);
			ReentrantLock loadLock = lockId(nodeId);
			try
			{
				if (nodeCache.get(nodeId) != null)
				{
					node = nodeCache.get(nodeId);
					return node;
				}
				if (!persistenceManager.loadLightNode(nodeId))
				{
					throw new NotFoundException("Node[" + nodeId + "] not found.");
				}
				nodeCache.put(nodeId, node);
				return node;
			}
			finally
			{
				loadLock.unlock();
			}
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public Node getReferenceNode() throws NotFoundException
		public virtual Node getReferenceNode()
		{
			if (referenceNodeId == -1)
			{
				throw new NotFoundException("No reference node set");
			}
			return getNodeById(referenceNodeId);
		}

		internal virtual void setReferenceNodeId(int nodeId)
		{
			this.referenceNodeId = nodeId;
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public Relationship getRelationshipById(int relId) throws NotFoundException
		public virtual Relationship getRelationshipById(int relId)
		{
			RelationshipImpl relationship = relCache.get(relId);
			if (relationship != null)
			{
				return new RelationshipProxy(relId, this);
			}
			relationship = new RelationshipImpl(relId, this);
			ReentrantLock loadLock = lockId(relId);
			try
			{
				if (relCache.get(relId) != null)
				{
					relationship = relCache.get(relId);
					return new RelationshipProxy(relId, this);
				}
				RelationshipData data = persistenceManager.loadLightRelationship(relId);
				if (data == null)
				{
					throw new NotFoundException("Relationship[" + relId + "]");
				}
				int typeId = data.relationshipType();
				RelationshipType type = getRelationshipTypeById(typeId);
				if (type == null)
				{
					throw new NotFoundException("Relationship[" + data.getId() + "] exist but relationship type[" + typeId + "] not found.");
				}
//JAVA TO VB & C# CONVERTER WARNING: The original Java variable was marked 'final':
//ORIGINAL LINE: final int startNodeId = data.firstNode();
				int startNodeId = data.firstNode();
//JAVA TO VB & C# CONVERTER WARNING: The original Java variable was marked 'final':
//ORIGINAL LINE: final int endNodeId = data.secondNode();
				int endNodeId = data.secondNode();
				relationship = new RelationshipImpl(relId, startNodeId, endNodeId, type, false, this);
				relCache.put(relId, relationship);
				return new RelationshipProxy(relId, this);
			}
			finally
			{
				loadLock.unlock();
			}
		}

		internal virtual RelationshipType getRelationshipTypeById(int id)
		{
			return relTypeHolder.getRelationshipType(id);
		}

		internal virtual Relationship getRelForProxy(int relId)
		{
			RelationshipImpl relationship = relCache.get(relId);
			if (relationship != null)
			{
				return relationship;
			}
			relationship = new RelationshipImpl(relId, this);
			ReentrantLock loadLock = lockId(relId);
			try
			{
				if (relCache.get(relId) != null)
				{
					relationship = relCache.get(relId);
					return relationship;
				}
				RelationshipData data = persistenceManager.loadLightRelationship(relId);
				if (data == null)
				{
					throw new NotFoundException("Relationship[" + relId + "] not found.");
				}
				int typeId = data.relationshipType();
				RelationshipType type = getRelationshipTypeById(typeId);
				if (type == null)
				{
					throw new NotFoundException("Relationship[" + data.getId() + "] exist but relationship type[" + typeId + "] not found.");
				}
				relationship = new RelationshipImpl(relId, data.firstNode(), data.secondNode(), type, false, this);
				relCache.put(relId, relationship);
				return relationship;
			}
			finally
			{
				loadLock.unlock();
			}
		}

		public virtual void removeNodeFromCache(int nodeId)
		{
			nodeCache.Remove(nodeId);
		}

		public virtual void removeRelationshipFromCache(int id)
		{
			relCache.Remove(id);
		}

		internal virtual object loadPropertyValue(int id)
		{
			return persistenceManager.loadPropertyValue(id);
		}

		internal virtual RelationshipChainPosition getRelationshipChainPosition(NodeImpl node)
		{
			return persistenceManager.getRelationshipChainPosition((int) node.getId());
		}

		internal virtual ArrayMap<string, IntArray> getMoreRelationships(NodeImpl node)
		{
			int nodeId = (int) node.getId();
			RelationshipChainPosition position = node.getRelChainPosition();
			IEnumerable <RelationshipData> rels = persistenceManager.getMoreRelationships(nodeId, position);
			ArrayMap<string, IntArray> newRelationshipMap = new ArrayMap<string, IntArray>();
			foreach (RelationshipData rel in rels)
			{
				int relId = rel.getId();
				RelationshipImpl relImpl = relCache.get(relId);
				RelationshipType type = null;
				if (relImpl == null)
				{
					type = getRelationshipTypeById(rel.relationshipType());
					Debug.Assert(type != null);
					relImpl = new RelationshipImpl(relId, rel.firstNode(), rel.secondNode(), type, false, this);
					relCache.put(relId, relImpl);
				}
				else
				{
					type = relImpl.getType();
				}
				IntArray relationshipSet = newRelationshipMap.get(type.name());
				if (relationshipSet == null)
				{
					relationshipSet = new IntArray();
					newRelationshipMap.put(type.name(), relationshipSet);
				}
				relationshipSet.Add(relId);
			}
			return newRelationshipMap;
		}

		internal virtual ArrayMap<int, PropertyData> loadProperties(NodeImpl node)
		{
			return persistenceManager.loadNodeProperties((int) node.getId());
		}

		internal virtual ArrayMap<int, PropertyData> loadProperties(RelationshipImpl relationship)
		{
			return persistenceManager.loadRelProperties((int) relationship.getId());
		}

		internal virtual int getNodeMaxCacheSize()
		{
		// return nodeCache.maxSize();
			return -1;
		}

		internal virtual int getRelationshipMaxCacheSize()
		{
		// return relCache.maxSize();
			return -1;
		}

		public virtual void clearCache()
		{
			nodeCache.Clear();
			relCache.Clear();
		}

		internal virtual void setRollbackOnly()
		{
			try
			{
				transactionManager.setRollbackOnly();
			}
			catch (IllegalStateException e)
			{
			// this exception always get generated in a finally block and 
			// when it happens another exception has already been thrown
			// (most likley NotInTransactionException)
				log.log(Level.FINE, "Failed to set transaction rollback only", e);
			}
			catch (javax.transaction.SystemException se)
			{
			// our TM never throws this exception
				log.log(Level.SEVERE, "Failed to set transaction rollback only", se);
			}
		}

		internal virtual void acquireLock(object resource, LockType lockType)
		{
			if (lockType == LockType.READ)
			{
				lockManager.getReadLock(resource);
			}
			else if (lockType == LockType.WRITE)
			{
				lockManager.getWriteLock(resource);
			}
			else
			{
				throw new LockException("Unknown lock type: " + lockType);
			}
		}

		internal virtual void releaseLock(object resource, LockType lockType)
		{
			if (lockType == LockType.READ)
			{
				lockManager.releaseReadLock(resource);
			}
			else if (lockType == LockType.WRITE)
			{
				lockReleaser.addLockToTransaction(resource, lockType);
			}
			else
			{
				throw new LockException("Unknown lock type: " + lockType);
			}
		}

		public virtual long getHighestPossibleIdInUse<T1>(System.Type<T1> clazz)
		{
			return idGenerator.getHighestPossibleIdInUse(clazz);
		}

		public virtual long getNumberOfIdsInUse<T1>(System.Type<T1> clazz)
		{
			return idGenerator.getNumberOfIdsInUse(clazz);
		}

		public virtual void removeRelationshipTypeFromCache(int id)
		{
			relTypeHolder.removeRelType(id);
		}

		internal virtual Traverser createTraverser(Order traversalOrder, NodeImpl node, RelationshipType relationshipType, Direction direction, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator)
		{
			return traverserFactory.createTraverser(traversalOrder, new NodeProxy((int) node.getId(), this), relationshipType, direction, stopEvaluator, returnableEvaluator);
		}

		internal virtual Traverser createTraverser(Order traversalOrder, NodeImpl node, RelationshipType[] types, Direction[] dirs, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator)
		{
			return traverserFactory.createTraverser(traversalOrder, new NodeProxy((int) node.getId(), this), types, dirs, stopEvaluator, returnableEvaluator);
		}

		internal virtual void addPropertyIndexes(PropertyIndexData[] propertyIndexes)
		{
			propertyIndexManager.addPropertyIndexes(propertyIndexes);
		}

		internal virtual void setHasAllpropertyIndexes(bool hasAll)
		{
			propertyIndexManager.setHasAll(hasAll);
		}

		internal virtual void clearPropertyIndexes()
		{
			propertyIndexManager.Clear();
		}

		internal virtual PropertyIndex getIndexFor(int keyId)
		{
			return propertyIndexManager.getIndexFor(keyId);
		}

		internal virtual IEnumerable <PropertyIndex> index(string key)
		{
			return propertyIndexManager.index(key);
		}

		internal virtual bool hasAllPropertyIndexes()
		{
			return propertyIndexManager.hasAll();
		}

		internal virtual bool hasIndexFor(int keyId)
		{
			return propertyIndexManager.hasIndexFor(keyId);
		}

		internal virtual PropertyIndex createPropertyIndex(string key)
		{
			return propertyIndexManager.createPropertyIndex(key);
		}

		internal virtual int getRelationshipTypeIdFor(RelationshipType type)
		{
			return relTypeHolder.getIdFor(type);
		}

		internal virtual void addRawRelationshipTypes(RelationshipTypeData[] relTypes)
		{
			relTypeHolder.addRawRelationshipTypes(relTypes);
		}

		internal virtual IEnumerable <RelationshipType> getRelationshipTypes()
		{
			return relTypeHolder.getRelationshipTypes();
		}

		internal virtual void deleteNode(NodeImpl node)
		{
			int nodeId = (int) node.getId();
			persistenceManager.nodeDelete(nodeId);
		// remove from node cache done via event
		}

		internal virtual int nodeAddProperty(NodeImpl node, PropertyIndex index, object @value)
		{
			int nodeId = (int) node.getId();
			return persistenceManager.nodeAddProperty(nodeId, index, @value);
		}

		internal virtual void nodeChangeProperty(NodeImpl node, int propertyId, object @value)
		{
			int nodeId = (int) node.getId();
			persistenceManager.nodeChangeProperty(nodeId, propertyId, @value);
		}

		internal virtual void nodeRemoveProperty(NodeImpl node, int propertyId)
		{
			int nodeId = (int) node.getId();
			persistenceManager.nodeRemoveProperty(nodeId, propertyId);
		}

		internal virtual void deleteRelationship(RelationshipImpl rel)
		{
			int relId = (int) rel.getId();
			persistenceManager.relDelete(relId);
		// remove in rel cache done via event
		}

		internal virtual int relAddProperty(RelationshipImpl rel, PropertyIndex index, object @value)
		{
			int relId = (int) rel.getId();
			return persistenceManager.relAddProperty(relId, index, @value);
		}

		internal virtual void relChangeProperty(RelationshipImpl rel, int propertyId, object @value)
		{
			int relId = (int) rel.getId();
			persistenceManager.relChangeProperty(relId, propertyId, @value);
		}

		internal virtual void relRemoveProperty(RelationshipImpl rel, int propertyId)
		{
			int relId = (int) rel.getId();
			persistenceManager.relRemoveProperty(relId, propertyId);
		}

		public virtual IntArray getCowRelationshipRemoveMap(NodeImpl node, string type)
		{
			return lockReleaser.getCowRelationshipRemoveMap(node, type);
		}

		public virtual IntArray getCowRelationshipRemoveMap(NodeImpl node, string type, bool create)
		{
			return lockReleaser.getCowRelationshipRemoveMap(node, type, create);
		}

		public virtual ArrayMap<string, IntArray> getCowRelationshipAddMap(NodeImpl node)
		{
			return lockReleaser.getCowRelationshipAddMap(node);
		}

		public virtual IntArray getCowRelationshipAddMap(NodeImpl node, string @string)
		{
			return lockReleaser.getCowRelationshipAddMap(node, @string);
		}

		public virtual IntArray getCowRelationshipAddMap(NodeImpl node, string @string, bool create)
		{
			return lockReleaser.getCowRelationshipAddMap(node, @string, create);
		}

		public virtual NodeImpl getNodeIfCached(int nodeId)
		{
			return nodeCache.get(nodeId);
		}

		public virtual RelationshipImpl getRelIfCached(int nodeId)
		{
			return relCache.get(nodeId);
		}

		public virtual ArrayMap<int, PropertyData> getCowPropertyRemoveMap(NeoPrimitive primitive)
		{
			return lockReleaser.getCowPropertyRemoveMap(primitive);
		}

		public virtual ArrayMap<int, PropertyData> getCowPropertyAddMap(NeoPrimitive primitive)
		{
			return lockReleaser.getCowPropertyAddMap(primitive);
		}

		public virtual ArrayMap<int, PropertyData> getCowPropertyAddMap(NeoPrimitive primitive, bool create)
		{
			return lockReleaser.getCowPropertyAddMap(primitive, create);
		}

		public virtual ArrayMap<int, PropertyData> getCowPropertyRemoveMap(NeoPrimitive primitive, bool create)
		{
			return lockReleaser.getCowPropertyRemoveMap(primitive, create);
		}

		internal virtual LockReleaser getLockReleaser()
		{
			return this.lockReleaser;
		}

		internal virtual void addRelationshipType(RelationshipTypeData type)
		{
			relTypeHolder.addRawRelationshipType(type);
		}

		internal virtual void addPropertyIndex(PropertyIndexData index)
		{
			propertyIndexManager.addPropertyIndex(index);
		}
	}
}